﻿// HSS.IO.Zip.Crc32.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       Crc32.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.IO.Zip
{
	#region Using Directives
	using System;
	#endregion

	#region CRC32
	/// <summary>
	/// Calculates a 32bit Cyclic Redundancy Checksum (CRC) using the
	/// same polynomial used by Zip. This type ie generally not used directly
	/// by applications wishing to create, read, or manipulate zip archive files.
	/// </summary>
	public class CRC32
	{
		/// <summary>
		/// indicates the total number of bytes read on the CRC stream.
		/// This is used when writing the ZipDirEntry when compressing files.
		/// </summary>
		public Int32 TotalBytesRead
		{
			get
			{
				return _TotalBytesRead;
			}
		}

		/// <summary>
		/// Indicates the current CRC for all blocks slurped in.
		/// </summary>
		public Int32 Crc32Result
		{
			get
			{
				// return one's complement of the running result
				return (Int32)(~_RunningCrc32Result);
			}
		}

		/// <summary>
		/// Returns the CRC32 for the specified stream.
		/// </summary>
		/// <param name="input">The stream over which to calculate the CRC32</param>
		/// <returns>the CRC32 calculation</returns>
		public Int32 GetCrc32(System.IO.Stream input)
		{
			return GetCrc32AndCopy(input, null);
		}

		/// <summary>
		/// Returns the CRC32 for the specified stream, and writes the input into the output stream.
		/// </summary>
		/// <param name="input">The stream over which to calculate the CRC32</param>
		/// <param name="output">The stream into which to deflate the input</param>
		/// <returns>the CRC32 calculation</returns>
		public Int32 GetCrc32AndCopy(System.IO.Stream input, System.IO.Stream output)
		{
			if (input == null)
				throw new ZipException("bad input.", new ArgumentException("The input stream must not be null.", "input"));

			unchecked
			{
				//UInt32 crc32Result;
				//crc32Result = 0xFFFFFFFF;
				byte[] buffer = new byte[BUFFER_SIZE];
				int readSize = BUFFER_SIZE;

				_TotalBytesRead = 0;
				int count = input.Read(buffer, 0, readSize);
				if (output != null) output.Write(buffer, 0, count);
				_TotalBytesRead += count;
				while (count > 0)
				{
					//for (int i = 0; i < count; i++)
					//{
					//    _RunningCrc32Result = ((_RunningCrc32Result) >> 8) ^ crc32Table[(buffer[i]) ^ ((_RunningCrc32Result) & 0x000000FF)];
					//}


					SlurpBlock(buffer, 0, count);
					count = input.Read(buffer, 0, readSize);
					if (output != null) output.Write(buffer, 0, count);
					_TotalBytesRead += count;
				}

				return (Int32)(~_RunningCrc32Result);
			}
		}


		/// <summary>
		/// Get the CRC32 for the given (word,byte) combo. 
		/// This is a computation defined by PKzip.
		/// </summary>
		/// <param name="W">The word to start with.</param>
		/// <param name="B">The byte to combine it with.</param>
		/// <returns>The CRC-ized result.</returns>
		public Int32 ComputeCrc32(Int32 W, byte B)
		{
			return ComputeCrc32((UInt32)W, B);
		}

		internal Int32 ComputeCrc32(UInt32 W, byte B)
		{
			return (Int32)(crc32Table[(W ^ B) & 0xFF] ^ (W >> 8));
		}

		/// <summary>
		/// Update the value for the running CRC32 using the given block of bytes.
		/// This is useful when using the CRC32() class in a Stream.
		/// </summary>
		/// <param name="block">block of bytes to slurp</param>
		/// <param name="offset">starting point in the block</param>
		/// <param name="count">how many bytes within the block to slurp</param>
		public void SlurpBlock(byte[] block, int offset, int count)
		{
			if (block == null)
				throw new ZipException("Bad buffer.", new ArgumentException("The data buffer must not be null.", "block"));

			for (int i = 0; i < count; i++)
			{
				int x = offset + i;
				_RunningCrc32Result = ((_RunningCrc32Result) >> 8) ^ crc32Table[(block[x]) ^ ((_RunningCrc32Result) & 0x000000FF)];
			}
			_TotalBytesRead += count;
		}


		/// <summary>
		/// Construct an instance of the CRC32 class, pre-initialising the table
		/// for speed of lookup.
		/// </summary>
		public CRC32()
		{
			unchecked
			{
				// This is the official polynomial used by CRC32 in PKZip.
				// Often the polynomial is shown reversed as 0x04C11DB7.
				UInt32 dwPolynomial = 0xEDB88320;
				UInt32 i, j;

				crc32Table = new UInt32[256];

				UInt32 dwCrc;
				for (i = 0; i < 256; i++)
				{
					dwCrc = i;
					for (j = 8; j > 0; j--)
					{
						if ((dwCrc & 1) == 1)
						{
							dwCrc = (dwCrc >> 1) ^ dwPolynomial;
						}
						else
						{
							dwCrc >>= 1;
						}
					}
					crc32Table[i] = dwCrc;
				}
			}
		}


		// private member vars
		private Int32 _TotalBytesRead;
		private static UInt32[] crc32Table;
		private const int BUFFER_SIZE = 8192;
		private UInt32 _RunningCrc32Result = 0xFFFFFFFF;

	}




	/// <summary>
	/// A read-only, forward-only Stream that calculates a CRC, a checksum, on all bytes read, 
	/// or on all bytes written.
	/// </summary>
	///
	/// <remarks>
	/// This class can be used to verify the CRC of a ZipEntry when reading from a stream, 
	/// or to calculate a CRC when writing to a stream.  The stream should be used to either 
	/// read, or write, but not both.  If you intermix reads and writes, the results are
	/// not defined.
	/// </remarks>
	public class CrcCalculatorStream : System.IO.Stream
	{
		private System.IO.Stream _InnerStream;
		private CRC32 _Crc32;
		private int _length = 0;

		/// <summary>
		/// Gets the total number of bytes run through the CRC32 calculator.
		/// </summary>
		///
		/// <remarks>
		/// This is either the total number of bytes read, or the total number
		/// of bytes written, depending on the direction of this stream.
		/// </remarks>
		public int TotalBytesSlurped
		{
			get { return _Crc32.TotalBytesRead; }
		}


		/// <summary>
		/// The constructor.
		/// </summary>
		/// <param name="stream">The underlying stream</param>
		public CrcCalculatorStream(System.IO.Stream stream)
			: base()
		{
			_InnerStream = stream;
			_Crc32 = new CRC32();

		}

		/// <summary>
		/// The constructor.
		/// </summary>
		/// <param name="stream">The underlying stream</param>
		/// <param name="length">The length of the stream to slurp</param>
		public CrcCalculatorStream(System.IO.Stream stream, int length)
			: base()
		{
			_InnerStream = stream;
			_Crc32 = new CRC32();
			_length = length;
		}

		/// <summary>
		/// Provides the current CRC for all blocks slurped in.
		/// </summary>
		public Int32 Crc32
		{
			get { return _Crc32.Crc32Result; }
		}


		/// <summary>
		/// Read from the stream
		/// </summary>
		/// <param name="buffer">the buffer to read</param>
		/// <param name="offset">the offset at which to start</param>
		/// <param name="count">the number of bytes to read</param>
		/// <returns>the number of bytes actually read</returns>
		public override int Read(byte[] buffer, int offset, int count)
		{
			int bytesToRead = count;

			// Need to limit the # of bytes returned, if the stream is intended to have a definite length.
			// This is especially useful when returning a stream for the uncompressed data directly to the 
			// application.  The app won't necessarily read only the UncompressedSize number of bytes.  
			// For example wrapping the stream returned from OpenReader() into a StreadReader() and
			// calling ReadToEnd() on it, We can "over-read" the zip data and get a corrupt string.  
			// The length limits that, prevents that problem. 

			if (_length != 0)
			{
				if (_Crc32.TotalBytesRead >= _length) return 0; // EOF
				int bytesRemaining = _length - _Crc32.TotalBytesRead;
				if (bytesRemaining < count) bytesToRead = bytesRemaining;
			}
			int n = _InnerStream.Read(buffer, offset, bytesToRead);
			if (n > 0) _Crc32.SlurpBlock(buffer, offset, n);
			return n;
		}

		/// <summary>
		/// Write to the stream. 
		/// </summary>
		/// <param name="buffer">the buffer from which to write</param>
		/// <param name="offset">the offset at which to start writing</param>
		/// <param name="count">the number of bytes to write</param>
		public override void Write(byte[] buffer, int offset, int count)
		{
			if (count > 0) _Crc32.SlurpBlock(buffer, offset, count);
			_InnerStream.Write(buffer, offset, count);
		}

		/// <summary>
		/// Indicates whether the stream supports reading. Always returns true.
		/// </summary>
		public override bool CanRead
		{
			get { return true; }
		}

		/// <summary>
		/// Indicates whether the stream supports seeking. Always returns false.
		/// </summary>
		public override bool CanSeek
		{
			get { return false; }
		}

		/// <summary>
		/// Indicates whether the stream supports writing. Always returns true.
		/// </summary>
		public override bool CanWrite
		{
			get { return true; }
		}

		/// <summary>
		/// Not implemented.
		/// </summary>
		public override void Flush()
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Not implemented.
		/// </summary>
		public override long Length
		{
			get
			{
				if (_length == 0) throw new NotImplementedException();
				else return _length;
			}
		}

		/// <summary>
		/// Not implemented.
		/// </summary>
		public override long Position
		{
			get { return _Crc32.TotalBytesRead; }
			set { throw new NotImplementedException(); }
		}

		/// <summary>
		/// Not implemented.
		/// </summary>
		/// <param name="offset">N/A</param>
		/// <param name="origin">N/A</param>
		/// <returns>N/A</returns>
		public override long Seek(long offset, System.IO.SeekOrigin origin)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Not implemented.
		/// </summary>
		/// <param name="value">N/A</param>
		public override void SetLength(long value)
		{
			throw new NotImplementedException();
		}
	}
	#endregion
}